/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.data.values;

import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;

import edu.ucsd.db.bassarisk.common.BassariskInternalError;
import edu.ucsd.db.bassarisk.data.api.IValueType;
import edu.ucsd.db.bassarisk.data.api.IValueType.IReferenceSort.ReferenceSortOption;

/*
 * ::EDIT WARNING:: This file is extremely similar to IntType.java in the same
 * package. Bug fixes (improvements) should be propagated to both files if
 * needed (possible).
 * 
 * The replication between these files is a necessary evil to reduce code
 * duplication elsewhere while maintaining high performance.
 * 
 * Thanks!
 */
public class LongType implements IValueType {
  private static final long serialVersionUID = 2414902046794555501L;

  public static class BufferedLongReference implements
      IBufferedValueReference<LongType> {
    private ByteBuffer buffer;
    private int length;
    private int startOffset;

    @Override
    public ByteBuffer getBuffer() {
      return this.buffer;
    }

    @Override
    public int getLength() {
      return this.length;
    }

    @Override
    public int getStartOffset() {
      return this.startOffset;
    }

    @Override
    public IValueType getType() {
      return LongType.INSTANCE;
    }

    @Override
    public void reset(final ByteBuffer newBuffer, final int newOffset,
                      final int newLength) {
      this.buffer = newBuffer;
      this.startOffset = newOffset;
      this.length = newLength;
    }

    @Override
    public void resetDefault() {
      this.buffer.putLong(this.startOffset, 0L);
    }

    @Override
    public void setBuffer(final ByteBuffer newBuffer) {
      this.buffer = newBuffer;
    }

    @Override
    public void setLength(final int newLength) {
      assert newLength == 8;
    }

    @Override
    public void setStartOffset(final int newOffset) {
      this.startOffset = newOffset;
    }

    @Override
    public String toString() {
      return "Buffered 64-bit Int: " + this.buffer.getLong(this.startOffset);
    }
  }

  public static class LongReferenceSort implements IReferenceSort {
    private final ReferenceSortOption option;

    public LongReferenceSort(final ReferenceSortOption option) {
      this.option = option;
    }

    @Override
    public IValueType getReferenceType() {
      return LongType.INSTANCE;
    }

    @Override
    public ReferenceSortOption getSortOption() {
      return this.option;
    }

    @Override
    public ITypeTools<?> getTypeTools() {
      return LongTools.INSTANCE;
    }
  }

  public static class LongTools implements ITypeTools<LongType> {
    public static final LongTools INSTANCE = new LongTools();

    private final LongValueCodec bufferedCodec;
    private final Comparator<IValueReference<LongType>> bufferedComparator;
    private final IValueCopier<LongType> bufferedCopier;

    private final LongValueCodec genericCodec;
    private final Comparator<IValueReference<LongType>> genericComparator;
    private final IValueCopier<LongType> genericCopier;

    private final LongValueCodec objectCodec;
    private final Comparator<IValueReference<LongType>> objectComparator;
    private final IValueCopier<LongType> objectCopier;

    private LongTools() {

      this.genericComparator = new Comparator<IValueReference<LongType>>() {
        @Override
        public int compare(final IValueReference<LongType> g1,
                           final IValueReference<LongType> g2) {
          long v1 = LongTools.this.genericCodec.getLongValue(g1);
          long v2 = LongTools.this.genericCodec.getLongValue(g2);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.objectComparator = new Comparator<IValueReference<LongType>>() {
        @Override
        public int compare(final IValueReference<LongType> g1,
                           final IValueReference<LongType> g2) {
          long v1 = LongTools.this.objectCodec.getLongValue(g1);
          long v2 = LongTools.this.objectCodec.getLongValue(g2);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.bufferedComparator = new Comparator<IValueReference<LongType>>() {
        @Override
        public int compare(final IValueReference<LongType> g1,
                           final IValueReference<LongType> g2) {
          // TODO (nbales) - Implement direct binary comparison
          long v1 = LongTools.this.bufferedCodec.getLongValue(g1);
          long v2 = LongTools.this.bufferedCodec.getLongValue(g1);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.genericCopier = new IValueCopier<LongType>() {
        @Override
        public void copy(final IValueReference<LongType> valueSource,
                         final IValueReference<LongType> valueDestination) {
          LongTools.this.genericCodec.setLongValue(LongTools.this.genericCodec
              .getLongValue(valueSource), valueDestination);
        }
      };

      this.objectCopier = new IValueCopier<LongType>() {
        @Override
        public void copy(final IValueReference<LongType> valueSource,
                         final IValueReference<LongType> valueDestination) {
          LongTools.this.objectCodec.setLongValue(LongTools.this.objectCodec
              .getLongValue(valueSource), valueDestination);
        }
      };

      this.bufferedCopier = new IValueCopier<LongType>() {
        @Override
        public void copy(final IValueReference<LongType> valueSource,
                         final IValueReference<LongType> valueDestination) {
          BufferedLongReference bufferedSource =
              (BufferedLongReference) valueDestination;
          BufferedLongReference bufferedDestination =
              (BufferedLongReference) valueDestination;
          System.arraycopy(bufferedSource.buffer.array(), bufferedSource.length,
                           bufferedDestination.buffer.array(),
                           bufferedDestination.length, bufferedSource.length);
        }
      };

      this.genericCodec = new LongValueCodec() {
        @Override
        public long getLongValue(final IValueReference<LongType> valueSource) {
          long value = 0;
          if (valueSource instanceof BufferedLongReference) {
            BufferedLongReference buffered = (BufferedLongReference) valueSource;
            value = buffered.buffer.getLong(buffered.startOffset);
          } else if (valueSource instanceof ObjectLongReference) {
            value = ((ObjectLongReference) valueSource).value;
          } else {
            String message = "Encountered unanticipated value reference type ";
            message += valueSource.getClass().getName();
            throw new BassariskInternalError(message);
          }
          return value;
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<LongType> valueSource,
                                      final IBufferedValueReference<LongType> valueDestination) {
          LongTools.this.bufferedCodec.setLongValue(this.getLongValue(valueSource),
                                                    valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<LongType> valueSource,
                                      final IObjectValueReference<LongType> valueDestination) {
          LongTools.this.objectCodec.setLongValue(this.getLongValue(valueSource),
                                                  valueDestination);
        }

        @Override
        public void setLongValue(final long value,
                                 final IValueReference<LongType> valueDestination) {
          assert valueDestination != null;
          if (valueDestination instanceof BufferedLongReference) {
            BufferedLongReference buffered =
                (BufferedLongReference) valueDestination;
            buffered.buffer.putLong(buffered.startOffset, value);
          } else if (valueDestination instanceof ObjectLongReference) {
            ((ObjectLongReference) valueDestination).value = value;
          } else {
            String message = "Encountered unanticipated value reference type ";
            message += valueDestination.getClass().getName();
            throw new BassariskInternalError(message);
          }
        }
      };

      this.objectCodec = new LongValueCodec() {
        @Override
        public long getLongValue(final IValueReference<LongType> valueSource) {
          assert valueSource instanceof ObjectLongReference;
          BufferedLongReference buffered = (BufferedLongReference) valueSource;
          return buffered.buffer.getLong(buffered.startOffset);
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<LongType> valueSource,
                                      final IBufferedValueReference<LongType> valueDestination) {
          LongTools.this.bufferedCodec.setLongValue(this.getLongValue(valueSource),
                                                    valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<LongType> valueSource,
                                      final IObjectValueReference<LongType> valueDestination) {
          LongTools.this.objectCodec.setLongValue(this.getLongValue(valueSource),
                                                  valueDestination);
        }

        @Override
        public void setLongValue(final long value,
                                 final IValueReference<LongType> valueDestination) {
          assert valueDestination instanceof ObjectLongReference;
          ((ObjectLongReference) valueDestination).value = value;
        }
      };

      this.bufferedCodec = new LongValueCodec() {
        @Override
        public long getLongValue(final IValueReference<LongType> valueSource) {
          assert valueSource instanceof BufferedLongReference;
          return ((ObjectLongReference) valueSource).value;
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<LongType> valueSource,
                                      final IBufferedValueReference<LongType> valueDestination) {
          LongTools.this.bufferedCodec.setLongValue(this.getLongValue(valueSource),
                                                    valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<LongType> valueSource,
                                      final IObjectValueReference<LongType> valueDestination) {
          LongTools.this.objectCodec.setLongValue(this.getLongValue(valueSource),
                                                  valueDestination);
        }

        @Override
        public void setLongValue(final long value,
                                 final IValueReference<LongType> valueDestination) {
          assert valueDestination instanceof BufferedLongReference;
          BufferedLongReference buffered = (BufferedLongReference) valueDestination;
          buffered.buffer.putLong(buffered.startOffset, value);
        }
      };
    };

    @Override
    public LongValueCodec getFastCodec(final IReferenceSort refType) {
      ReferenceSortOption option = refType.getSortOption();
      switch (option) {
      case JAVA_OBJECT:
        return this.objectCodec;
      case BYTE_BUFFER_SERIALIZED:
        return this.bufferedCodec;
      default:
        String message =
            String
                .format("Using generic long codec when fast comparator requested. "
                    + "Potential to increase performance by implementing fast long "
                    + "codec for %s", option.toString());
        LOG.log(Level.INFO, message);
        return this.genericCodec;
      }
    }

    @Override
    public Comparator<IValueReference<LongType>>
        getFastComparator(final IReferenceSort leftRefType,
                          final IReferenceSort rightRefType) {
      ReferenceSortOption leftOption = leftRefType.getSortOption();
      ReferenceSortOption rightOption = rightRefType.getSortOption();
      if (leftOption == ReferenceSortOption.JAVA_OBJECT
          && rightOption == ReferenceSortOption.JAVA_OBJECT) {
        return this.objectComparator;
      } else if (leftOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED
          && rightOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED) {
        return this.bufferedComparator;
      } else {
        String message =
            String
                .format("Using generic long Comparator when fast comparator requested. "
                            + "Potential to increase performance by implementing fast long "
                            + "comparator for %s and %s", leftOption.toString(),
                        rightRefType.toString());
        LOG.log(Level.INFO, message);
        return this.genericComparator;
      }
    }

    @Override
    public IValueCopier<LongType> getFastCopier(final IReferenceSort leftRefType,
                                                final IReferenceSort rightRefType) {
      ReferenceSortOption leftOption = leftRefType.getSortOption();
      ReferenceSortOption rightOption = rightRefType.getSortOption();
      if (leftOption == ReferenceSortOption.JAVA_OBJECT
          && rightOption == ReferenceSortOption.JAVA_OBJECT) {
        return this.objectCopier;
      } else if (leftOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED
          && rightOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED) {
        return this.bufferedCopier;
      } else {
        String message =
            String
                .format("Using generic long Copier when fast comparator requested. "
                            + "Potential to increase performance by implementing fast long "
                            + "copier for %s and %s", leftOption.toString(),
                        rightRefType.toString());
        LOG.log(Level.INFO, message);
        return this.genericCopier;
      }
    }

    @Override
    public LongValueCodec getGenericCodec() {
      return this.genericCodec;
    }

    @Override
    public Comparator<IValueReference<LongType>> getGenericComparator() {
      return this.genericComparator;
    }

    @Override
    public IValueCopier<LongType> getGenericCopyer() {
      return this.genericCopier;
    }

    @Override
    public BufferedLongReference newBufferedReference() {
      return new BufferedLongReference();
    }

    @Override
    public ObjectLongReference newReference() {
      return new ObjectLongReference();
    }
  };

  public static interface LongValueCodec extends IValueCodec<LongType> {
    public long getLongValue(final IValueReference<LongType> valueSource);

    public void setLongValue(final long value,
                             final IValueReference<LongType> valueDestination);
  }

  public static class ObjectLongReference implements IObjectValueReference<LongType> {
    private long value;

    public ObjectLongReference() {
      this.value = 0L;
    }

    public ObjectLongReference(final long value) {
      this.value = value;
    }

    @Override
    public IValueType getType() {
      return LongType.INSTANCE;
    }

    public long getValue() {
      return this.value;
    }

    public long increment() {
      return ++this.value;
    };

    @Override
    public void resetDefault() {
      this.value = 0L;
    }

    public void setValue(final long newValue) {
      this.value = newValue;
    }

    @Override
    public String toString() {
      return "Object 64bit Integer: " + this.value;
    }
  }

  public static final LongType INSTANCE = new LongType();

  private static final Logger LOG = Logger.getLogger(LongType.class.getName());

  private LongType() {}

  public long getUid() {
    return serialVersionUID;
  }
}
